package com.ztom.beans.factory

import cn.hutool.core.bean.BeanUtil
import cn.hutool.core.util.ClassUtil
import com.ztom.beans.exception.BeansException
import com.ztom.beans.factory.support.aware.BeanFactoryAware
import com.ztom.beans.factory.support.pojo.BeanDefinition
import com.ztom.beans.factory.support.pojo.BeanReference
import com.ztom.beans.factory.support.instantiate.InstantiationStrategy
import com.ztom.beans.factory.support.instantiate.SimpleInstantiationStrategy
import com.ztom.beans.factory.support.lifecycle.DisposableBean
import com.ztom.beans.factory.support.lifecycle.DisposableBeanAdapter
import com.ztom.beans.factory.support.lifecycle.InitializingBean

/**
 * 实现了模板方法 createBean
 *
 * AutowireCapable: 实现了自动注入
 *
 * @author ZhangTao
 */
abstract class AbstractAutowireCapableBeanFactory : AbstractBeanFactory(), AutowireCapableBeanFactory {

    var instantiationStrategy: InstantiationStrategy = SimpleInstantiationStrategy()

    override fun createBean(beanName: String, beanDefinition: BeanDefinition): Any {
        return doCreateBean(beanName, beanDefinition)
    }

    private fun doCreateBean(beanName: String, beanDefinition: BeanDefinition): Any {
        val bean: Any
        try {
            // 实例化
            bean = createBeanInstance(beanDefinition)
            // 填充属性
            applyPropertyValues(beanName, bean, beanDefinition)
            // 初始化
            initializeBean(beanName, bean, beanDefinition)
        } catch (e: Exception) {
            throw BeansException("Instantiation of bean failed", e)
        }

        // 注册有销毁方法的 bean
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition)

        // 注册单例 bean
        if (beanDefinition.isSingleton()) {
            addSingleton(beanName, bean)
        }
        return bean
    }

    private fun createBeanInstance(beanDefinition: BeanDefinition): Any {
        return instantiationStrategy.instantiate(beanDefinition)
    }

    private fun applyPropertyValues(beanName: String, bean: Any, beanDefinition: BeanDefinition) {
        try {
            beanDefinition.propertyValues.getPropertyValues().forEach {
                var value = it.value
                if (value is BeanReference) {
                    value = getBean(value.beanName)
                }
                // 反射赋值
                BeanUtil.setFieldValue(bean, it.name, value)
            }
        } catch (e: Exception) {
            throw BeansException("Error setting property values for bean: $beanName", e)
        }
    }

    private fun initializeBean(beanName: String, bean: Any, beanDefinition: BeanDefinition): Any {
        // 回调注入 beanFactory
        if (bean is BeanFactoryAware) {
            bean.setBeanFactory(this)
        }

        // 前置处理
        var wrappedBean = applyBeanPostProcessorBeforeInitialization(bean, beanName)
        // 初始化
        try {
            invokeInitMethods(beanName, wrappedBean, beanDefinition)
        } catch (e: Throwable) {
            throw BeansException("Invocation init method of bean [$beanName] failed", e)
        }
        // 后置处理
        wrappedBean = applyBeanPostProcessorAfterInitialization(wrappedBean, beanName)

        return wrappedBean
    }

    override fun applyBeanPostProcessorBeforeInitialization(existBean: Any, beanName: String): Any {
        var result = existBean
        getBeanPostProcessorList().forEach {
            val cur = it.postProcessBeforeInitialization(existBean, beanName)
            // 怕你瞎玩
            result = cur ?: result
        }
        return result
    }

    override fun applyBeanPostProcessorAfterInitialization(existBean: Any, beanName: String): Any {
        var result = existBean
        getBeanPostProcessorList().forEach {
            val cur = it.postProcessAfterInitialization(existBean, beanName)
            // 怕你瞎玩
            result = cur ?: result
        }
        return result
    }

    private fun invokeInitMethods(beanName: String, bean: Any, beanDefinition: BeanDefinition) {
        // 1.实现了 InitializingBean 接口
        if (bean is InitializingBean) {
            bean.afterPropertiesSet()
        }
        // 2.自定义了 init-method 方法
        val initMethodName = beanDefinition.initMethodName
        if (!initMethodName.isNullOrBlank() && (bean !is InitializingBean || initMethodName != "afterPropertiesSet")) {
            val initMethod = ClassUtil.getPublicMethod(bean::class.java, initMethodName)
                ?: throw BeansException("Cannot find a init method named '$initMethodName' on bean with name '$beanName'")
            initMethod.invoke(bean)
        }
    }

    private fun registerDisposableBeanIfNecessary(beanName: String, bean: Any, beanDefinition: BeanDefinition) {
        if (bean is DisposableBean || !beanDefinition.destroyMethodName.isNullOrBlank()) {
            registerDisposableBean(beanName, DisposableBeanAdapter(bean, beanName, beanDefinition))
        }
    }
}